package coderead.maven.job.scheduled;
/**
 * @Copyright 源码阅读网 http://coderead.cn
 */


import coderead.maven.job.bean.Artifact;
import coderead.maven.job.bean.ArtifactClass;
import coderead.maven.job.dao.ArtifactMapper;
import coderead.maven.job.properties.JobProperties;
import coderead.maven.job.util.NumberUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author 鲁班大叔
 * @date 2021
 */
@Slf4j
@Component
@Import(JobProperties.class)
@RequiredArgsConstructor
public class ClassIndexJob implements InitializingBean {

    private final ArtifactMapper artifactMapper;
    private final JobProperties jobProperties;

    private ThreadPoolExecutor downloadExecutor;

    private OkHttpClient okHttpClient;
    private Thread job;
    private final Map<Artifact, Integer> failTasks = new ConcurrentHashMap<>();
    int lastId = 0;
    long begin = System.currentTimeMillis();

    @Override
    public void afterPropertiesSet() {
        okHttpClient = new OkHttpClient()
                .newBuilder()
                .callTimeout(30, TimeUnit.SECONDS)
                .build();
        downloadExecutor = new ThreadPoolExecutor(jobProperties.getThreadSize(),
                jobProperties.getThreadSize(),
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>());
        if (jobProperties.isActive()) {
            runJob();
        }
    }

    private void runJob() {
        job = new Thread(() -> {
            while (true) {
                try {
                    log.info("执行进度:{} 用时{}", downloadExecutor, NumberUtil.distanceCurrentTime(begin));
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    log.error(e.getMessage(), e);
                }

                if (downloadExecutor.getQueue().size() < jobProperties.getTaskSize() * 2) {
                    int size = startUpdateTask(lastId);
                    if (size == 0) {
                        log.info("当前不存在待更新任务，休眠{}分钟", 60);
                        // 还原ID，执行下一次更新周期
                        lastId = 0;
                        try {
                            Thread.sleep(60 * 60 * 1000);
                        } catch (InterruptedException e) {
                            log.error("", e);
                        }
                        begin = System.currentTimeMillis();
                    }
                }
            }
        });
        job.setDaemon(true);
        job.start();
    }

    // 执行索引更新任务
    public int startUpdateTask(long beginId) {

        // 1. 获取待更新任务
        List<Artifact> artifacts = artifactMapper.findArtifactByState(beginId, ArtifactMapper.unIndex, jobProperties.getTaskSize());
        // where
        if (artifacts.isEmpty()) {
            return 0;
        }

        // 2.修改任务状态为进行中...
        Integer[] ids = new Integer[artifacts.size()];
        for (int i = 0; i < artifacts.size(); i++) {
            ids[i] = artifacts.get(i).getId();
        }
        artifactMapper.setArtifactSate(ids, ArtifactMapper.indexing);
        lastId = ids[ids.length - 1];
        // 3.添加至下载更新队列
        for (Artifact fact : artifacts) {
            downloadExecutor.submit(() -> {
                try {
                    tryDownloadArt(fact);
                } catch (Throwable e) {
                    log.error("", e);
                }
            });
        }
        return artifacts.size();
    }


    private boolean tryDownloadArt(Artifact artifact) {
        String[] downloadUrls = jobProperties.getJarDownloadUrls();
        for (String repositoryUrl : downloadUrls) {
            try {
                List<ArtifactClass> list = downloadJar(repositoryUrl, artifact);
                failTasks.remove(artifact);
                saveClassIndex(artifact, list);
                return true;
            } catch (IOException | IllegalStateException e) {
                log.error("下载或解析失败:{}:{}:{}", artifact.getGroupId(), artifact.getArtifactId(), artifact.getLastVersion(), e);
            } catch (Throwable ex) {
                log.error("更新失败，未知异常:{}:{}:{}", artifact.getGroupId(), artifact.getArtifactId(), artifact.getLastVersion(), ex);
                failTasks.remove(artifact);
                saveFailed(artifact);
                return false;
            }
        }
        int failCount = failTasks.getOrDefault(artifact, 0) + 1;
        failTasks.put(artifact, failCount);
        // 重试
        if (failCount < 2) {
            // 放至队列未尾
            downloadExecutor.submit(() -> tryDownloadArt(artifact));
        } else {
            failTasks.remove(artifact);
            log.error("下载或解析失败:{}:{}:{}", artifact.getGroupId(), artifact.getArtifactId(), artifact.getLastVersion());
            saveFailed(artifact);
        }
        return false;
    }

    private void saveFailed(Artifact artifact) {
        artifactMapper.setArtifactSate(new Integer[]{artifact.getId()}, ArtifactMapper.fail);
    }


    public List<ArtifactClass> downloadJar(String repositoryUrl, Artifact info) throws IOException {
//      示例  https://repo1.maven.org/maven2/com/alibaba/dubbo/2.6.10/dubbo-2.6.10.jar
        String urlText = String.format("%s/%s/%s/%s/%s-%s.jar",
                repositoryUrl,
                info.getGroupId().replaceAll("\\.", "/"),
                info.getArtifactId(),
                info.getLastVersion(),
                info.getArtifactId(),
                info.getLastVersion());
        log.debug("下载：{}", urlText);
        long begin = System.currentTimeMillis();
        InputStream inputStream;
        final Request request = new Request.Builder()
                .cacheControl(new CacheControl.Builder()
                        .noStore()
                        .build())
                .addHeader("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"")
                .addHeader("sec-ch-ua-mobile", "?0")
                .addHeader("Upgrade-Insecure-Requests", "1")
                .addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36")
                .url(urlText)
                .build();
        final Call call = okHttpClient.newCall(request);
        Response response = call.execute();
        if (!response.isSuccessful()) {
            response.close();
            throw new IllegalStateException(String.format("下载失败：状态码:%s  %s 链接：%s", response.code(), response.message(), urlText));
        }

        ResponseBody body = response.body();
        Assert.notNull(body, "Response body is null for URL: " + urlText);
        
        inputStream = body.byteStream();
        List<ArtifactClass> result = new ArrayList<>();
        try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
            ZipEntry zipEntry;
            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                String classFileName = zipEntry.getName();
                if (!classFileName.endsWith(".class")) {
                    continue;
                }
                // 必须要包含一个包名
                if (!classFileName.contains("/")) {
                    continue;
                }
                classFileName = classFileName.substring(0, classFileName.lastIndexOf("."));
                if (classFileName.endsWith("$")) {
                    continue;
                }
                String className = classFileName
                        .replaceAll("/", ".")
                        .replaceAll("\\$\\$", ".")
                        .replaceAll("\\$", ".");

                if (NumberUtil.isInteger(className.substring(className.lastIndexOf(".") + 1))) {
                    continue; // 过滤无名类部类
                }
                ArtifactClass artifactClass = new ArtifactClass();
                artifactClass.setArtifact(info.getArtifact());
                artifactClass.setVersion(info.getLastVersion());
                artifactClass.setClassname(className);
                result.add(artifactClass);
            }
        }
        response.close();
        if (result.isEmpty()) {
            log.warn("jar 包 class 数量为空：{}", urlText);
        }
        long end = System.currentTimeMillis();
        log.debug("下载用时：{} 秒,链接：{}", (end - begin) / 1000f, urlText);
        return result;
    }

    private void saveClassIndex(Artifact artifact, List<ArtifactClass> infos) throws IOException {
        if (infos.isEmpty()) {
            artifactMapper.setArtifactSate(new Integer[]{artifact.getId()}, ArtifactMapper.succeed);
            return;
        }
        artifactMapper.updateArtifactClass(infos);
    }


    public String getTaskInfo() {
        return String.format("执行进度:%s 用时%s",
                downloadExecutor,
                NumberUtil.distanceCurrentTime(begin));
    }
}
